#include "Core/application.h"

// #include "Core/time.h"
#include "Core/utils.h"

#include "Renderer/renderer.h"
#include "Core/input.h"

namespace CC
{

    const int FRAMES_PER_SECOND = 60;
    const float FRAMESTIME_PER_SECOND = (float)(1000.0 / (float)FRAMES_PER_SECOND);
    Application *Application::s_Instance = nullptr;

    Application::Application(const ApplicationSpecification &specification)
        : m_Specification(specification)
    {
        CC_CORE_INFO("Application::Application start\n");
        //FileLog::WriteFile("bin.txt", "abc");
        s_Instance = this;

        m_Window = Window::Create(WindowProps(m_Specification.Name, m_Specification.Width, m_Specification.Height, m_Specification.Floating, m_Specification.HasTitle));
        m_Window->SetEventCallback(std::bind(&Application::OnEvent, this, std::placeholders::_1));

        Renderer::Init();

        if (m_Specification.HasImGui) {
            m_ImGuiLayer = new ImGuiLayer();
            PushOverlay(m_ImGuiLayer);
        }        
    }

    Application::~Application()
    {
        Renderer::Shutdown();
        delete m_Window;
    }

    void Application::PushLayer(Layer *layer)
    {

        m_LayerStack.PushLayer(layer);
        layer->OnAttach();
    }

    void Application::PushOverlay(Layer *layer)
    {
        m_LayerStack.PushOverlay(layer);
        layer->OnAttach();
    }

    void Application::PopLayer(Layer *layer)
    {
        m_LayerStack.PopLayer(layer);
    }

    void Application::PopOverlay(Layer *layer)
    {
        m_LayerStack.PopOverlay(layer);
        layer->OnAttach();
    }

    void Application::Close()
    {
        m_Running = false;
    }

    void Application::SetWindowTitle(std::string name)
    {
        m_Specification.Name = name;
        m_Window->SetWindowTitle(name);
    }

    void Application::SubmitToMainThread(ThreadInfo& info)
    {
        m_MainThreadQueueMutex.lock();
        m_MainThreadQueue.emplace_back(info);
        m_MainThreadQueueMutex.unlock();
    }

    void Application::OnEvent(Event &e)
    {
        EventDispatcher dispatcher(e);
        dispatcher.Dispatch<WindowCloseEvent>(std::bind(&Application::OnWindowClose, this, std::placeholders::_1));
        dispatcher.Dispatch<WindowResizeEvent>(std::bind(&Application::OnWindowResize, this, std::placeholders::_1));

        for (auto it = m_LayerStack.rbegin(); it != m_LayerStack.rend(); ++it)
        {
            if (e.Handled)
                break;
            (*it)->OnEvent(e);
        }
    }

    void Application::Run()
    {
        static int fps_num = 0;

        while (m_Running)
        {

            float time = Time::GetTime();
            Timestep timestep = time - m_LastFrameTime;
            m_LastFrameTime = time;

            m_fpsTime += timestep.GetMilliseconds();
            fps_num += 1;

            if (m_fpsTime >= 1000.0f) {
                m_fpsCounter = fps_num;
                m_fpsTime = 0.0f;
                fps_num = 0;
            }
            else {
                
            }

            ExecuteMainThreadQueue();

            if (!m_Minimized)
            {
                {
                    for (Layer *layer : m_LayerStack)
                    {
                        layer->OnUpdate(timestep);
                    }
                }

                if (m_Specification.HasImGui) {
                    m_ImGuiLayer->Begin();
                    {
                        for (Layer *layer : m_LayerStack)
                            layer->OnImGuiRender();
                    }
                    m_ImGuiLayer->End();
                }               
            }

             //CC_CORE_INFO("m_Window OnUpdate=%f, timestep=%f, sleep=%f, %ul\n", m_LastFrameTime, timestep.GetMilliseconds(),
            //     FRAMESTIME_PER_SECOND - timestep.GetMilliseconds(),
            //     (DWORD)(FRAMESTIME_PER_SECOND - timestep.GetMilliseconds()));
            if (timestep.GetMilliseconds() < FRAMESTIME_PER_SECOND)
            {
#ifdef CC_PLATFORM_WINDOWS
                Sleep((DWORD)(FRAMESTIME_PER_SECOND - timestep.GetMilliseconds()));
#else
                Sleep((FRAMESTIME_PER_SECOND - timestep.GetMilliseconds()) * 0.001);
#endif
            }

            m_Window->OnUpdate();
        }
    }

    void Application::OnWindowClose(WindowCloseEvent &e)
    {
        CC_CORE_INFO("Application::OnWindowClose\n");
        m_Running = false;
    }

    void Application::OnWindowResize(WindowResizeEvent &e)
    {

        if (e.GetWidth() == 0 || e.GetHeight() == 0)
        {
            m_Minimized = true;
        }

        m_Minimized = false;
        Renderer::OnWindowResize(e.GetWidth(), e.GetHeight());
    }

    void Application::ExecuteMainThreadQueue()
    {
        m_MainThreadQueueMutex.lock();
        for (auto &info : m_MainThreadQueue)
            info.func(info.param);

        m_MainThreadQueue.clear();
        m_MainThreadQueueMutex.unlock();
    }
}
